/*********************************************************************************
 *Copyright(C),2015-2020, TUYA www.tuya.comm
 *FileName:    tuya_ipc_sd_demo
 **********************************************************************************/
#include "tuya_ipc_sd_demo.h"

/************************
Description: Using the TUYA SD card storage and playback function,
the developers need to implement the relevant interface.
Note：Interface names cannot be changed, and these interfaces are declared and used in SDK.
This demo file gives the management implementation of SD card operation for typical embedded devices in Linux system.
Developers can modify it according to their practical application.

************************/

#define DEF_TAG "TySDCard"

#define MAX_MMC_NAME_LEN 24
#define MAX_MOUNTS_INFO_LEN 1024
#define LINUX_SD_DEV_FILE "/dev/mmcblk0"
#define LINUX_MOUNT_INFO_FILE "/proc/mounts"
#define SD_MOUNT_PATH "/mnt/config/record"
#define FORMAT_CMD "mkfs.vfat"

#define TYDEBUG printf
#define TYERROR printf

extern INT_T g_sd_formatting;
extern TUYA_IPC_SDK_RUN_HANDLE s_ipc_sdk_run_handler;

STATIC CHAR_T s_mounts_info[MAX_MOUNTS_INFO_LEN];
STATIC CHAR_T s_mmcblk_name[MAX_MMC_NAME_LEN] = {0};
E_SD_STATUS g_sd_status = SD_STATUS_UNKNOWN;
StructSDStorage g_struct_sd_storage = {0};

// Implementation Interface of Formatting Operation
BOOL_T tuya_ipc_sd_format(VOID)
{
    CHAR_T format_cmd[256] = {0};
    char buffer[512] = {0};

    PRT_INFO(DEF_TAG, "sd format begin\n");

    strcpy(s_mmcblk_name, LINUX_SD_DEV_FILE);
    if (0 == access(LINUX_SD_DEV_FILE "p1", F_OK))
    {
        strcat(s_mmcblk_name, "p1");
    }

    snprintf(format_cmd, 256, "umount %s;%s %s;mount -t auto %s %s;", SD_MOUNT_PATH,
             FORMAT_CMD, s_mmcblk_name, s_mmcblk_name, SD_MOUNT_PATH);
    PRT_INFO(DEF_TAG, "execute: %s\n", format_cmd);
    FILE *pp = popen(format_cmd, "r");
    if (NULL != pp)
    {
        fgets(buffer, sizeof(buffer), pp);
        PRT_NORMAL("%s\n", buffer);
        pclose(pp);
    }
    else
    {
        PRT_ERR(DEF_TAG, "format_sd_card failed\n");
        return FALSE;
    }
    PRT_INFO(DEF_TAG, "sd format end\n");

    return TRUE;
}

// Implementation Interface for Remounting
VOID tuya_ipc_sd_remount(VOID)
{
    CHAR_T cmd[128] = {0};
    E_SD_STATUS status = SD_STATUS_UNKNOWN;

    status = tuya_ipc_sd_get_status();
    if (SD_STATUS_NORMAL == status)
    {
        TYDEBUG("sd don't need to remount!\n");
        return;
    }

    PRT_INFO(DEF_TAG, "remount sd card ..... \n");

    strcpy(s_mmcblk_name, LINUX_SD_DEV_FILE);
    if (0 == access(LINUX_SD_DEV_FILE "p1", F_OK))
    {
        strcat(s_mmcblk_name, "p1");
    }
    snprintf(cmd, 128, "umount %s;sleep 1;mount -t auto %s %s;",
             SD_MOUNT_PATH, s_mmcblk_name, SD_MOUNT_PATH);
    FILE *pp = popen(cmd, "r");
    if (NULL != pp)
    {
        PRT_DBG(DEF_TAG, "remount ok\n");
        pclose(pp);
    }
    else
    {
        printf("remount_sd_card failed\n");
    }
}

// Implementation Interface for mount
VOID tuya_ipc_sd_mount(VOID)
{
    CHAR_T cmd[128] = {0};

    PRT_INFO(DEF_TAG, "mount sd card ..... \n");

    strcpy(s_mmcblk_name, LINUX_SD_DEV_FILE);
    if (0 == access(LINUX_SD_DEV_FILE "p1", F_OK))
    {
        strcat(s_mmcblk_name, "p1");
    }

    snprintf(cmd, 128, "mount -t auto %s %s;", s_mmcblk_name, SD_MOUNT_PATH);
    FILE *pp = popen(cmd, "r");
    if (NULL != pp)
    {
        PRT_DBG(DEF_TAG, "mount ok\n");
        pclose(pp);
    }
    else
    {
        PRT_ERR(DEF_TAG, "mount_sd_card failed\n");
    }
}

// Implementation Interface for umount
BOOL_T tuya_ipc_sd_umount(VOID)
{
    CHAR_T cmd[128] = {0};

    PRT_INFO(DEF_TAG, "umount sd card.....\n");
    snprintf(cmd, 128, "umount %s;", SD_MOUNT_PATH);
    FILE *pp = popen(cmd, "r");
    if (NULL != pp)
    {
        PRT_DBG(DEF_TAG, "umount ok\n");
        pclose(pp);
    }
    else
    {
        PRT_ERR(DEF_TAG, "umount_sd_card failed\n");
        return FALSE;
    }

    return TRUE;
}

STREAM_STORAGE_WRITE_MODE_E tuya_ipc_sd_get_mode_config(VOID)
{
#if CMAKE_SUPPORT_SD_RECORD

    if (Ext_InComFile_Access(DEF_DP_ENABLE_SD_RECORD) == DEF_COM_FILE_NOT_EXIST)
    {
        return SS_WRITE_MODE_NONE;
    }

    UINT_T sd_mode = IPC_APP_get_sd_record_mode();
    if (sd_mode == 1)
    {
        /* all time mode */
        return SS_WRITE_MODE_ALL;
    }
    else
    {
        /* event mode */
        return SS_WRITE_MODE_EVENT;
    }
#else
    return SS_WRITE_MODE_ALL;
#endif
}

// Implementation Interface for Obtaining SD Card Status
E_SD_STATUS tuya_ipc_sd_get_status(VOID)
{
    if (Ext_InComFile_Access(LINUX_SD_DEV_FILE) == DEF_COM_FILE_NOT_EXIST)
    {
        return SD_STATUS_NOT_EXIST;
    }

    strcpy(s_mmcblk_name, LINUX_SD_DEV_FILE);
    if (0 == access(LINUX_SD_DEV_FILE "p1", F_OK)) // Default node name information
    {
        strcat(s_mmcblk_name, "p1");
    }

    FILE *fp;
    fp = fopen(LINUX_MOUNT_INFO_FILE, "rb");
    if (fp)
    {
        memset(s_mounts_info, 0, sizeof(s_mounts_info));
        fread(s_mounts_info, 1, MAX_MOUNTS_INFO_LEN, fp);
        fclose(fp);
        // Confirm the final node name in Mount information
        CHAR_T *mmcblk_name_start = strstr(s_mounts_info, s_mmcblk_name);

        /* sample : /dev/mmcblk0p1 /mnt/config/record vfat rw */
        CHAR_T *mmcblk_name_end = strstr(s_mounts_info, " " SD_MOUNT_PATH);
        if (mmcblk_name_start && mmcblk_name_end)
        {
            int mmcblk_name_len = mmcblk_name_end - mmcblk_name_start;
            if (mmcblk_name_len >= MAX_MMC_NAME_LEN)
            {
                return SD_STATUS_ABNORMAL;
            }
            strncpy(s_mmcblk_name, mmcblk_name_start, mmcblk_name_len);
            s_mmcblk_name[mmcblk_name_len] = '\0';
        }
        // There are device nodes but no mount information. Generally, the card format is incorrect and report abnormal.
        else
        {
            return SD_STATUS_ABNORMAL;
        }

        // If the mount information of the SD card is not at the end and there is a ro mount behind it, there will be a problem.
        if (NULL != strstr(mmcblk_name_start, "ro,"))
        {
            if (NULL != strstr(mmcblk_name_start, "vfat"))
            {
                return SD_STATUS_ABNORMAL;
            }
            else if (NULL == strstr(mmcblk_name_start, "rw,"))
            {
                return SD_STATUS_ABNORMAL;
            }
        }

        if ((NULL == strstr(mmcblk_name_start, "vfat")) && (NULL == strstr(mmcblk_name_start, "exfat")))
        {
            return SD_STATUS_ABNORMAL;
        }

        if (access(s_mmcblk_name, 0))
        {
            return SD_STATUS_ABNORMAL;
        }

        return SD_STATUS_NORMAL;
    }
    else
    {
        return SD_STATUS_UNKNOWN;
    }
}

// SD card capacity acquisition interface, unit: KB
VOID tuya_ipc_sd_get_capacity(UINT_T *p_total, UINT_T *p_used, UINT_T *p_free)
{
    *p_total = 0;
    *p_used = 0;
    *p_free = 0;

    struct statfs sd_fs;
    if (statfs(SD_MOUNT_PATH, &sd_fs) != 0)
    {
        TYERROR("statfs failed!\n");
        return;
    }

    *p_total = (UINT_T)(((UINT64_T)sd_fs.f_blocks * (UINT64_T)sd_fs.f_bsize) >> 10);
    *p_used = (UINT_T)((((UINT64_T)sd_fs.f_blocks - (UINT64_T)sd_fs.f_bfree) * (UINT64_T)sd_fs.f_bsize) >> 10);
    *p_free = (UINT_T)(((UINT64_T)sd_fs.f_bavail * (UINT64_T)sd_fs.f_bsize) >> 10);

    g_struct_sd_storage.total = *p_total;
    g_struct_sd_storage.used = *p_used;
    g_struct_sd_storage.free = *p_free;

    // TYDEBUG("sd capacity: total: %d KB, used %d KB, free %d KB\n", *p_total, *p_used, *p_free);
#if CMAKE_SUPPORT_SD_RECORD
    IPC_APP_report_sd_storage();
#endif
    return;
}

// get the path of mounting sdcard
CHAR_T *tuya_ipc_get_sd_mount_path(VOID)
{
    return SD_MOUNT_PATH;
}

void *check_sdcard_status_thread(void *arg)
{
#if CMAKE_SUPPORT_SD_RECORD
    unsigned char last_status = SD_STATUS_UNKNOWN;
    EnumSDcardState sd_state = SDCARD_STATE_MOUNTINT;

    tuya_ipc_sd_umount();
    tuya_ipc_sd_mount();

    while (g_ComThread_Running)
    {
        if (g_sd_formatting == DEF_COM_FALSE)
        {
            g_sd_status = tuya_ipc_sd_get_status();
            if (g_sd_status != last_status)
            {
                /**  Prevent SD card hot swap from reporting an SD card exception  **/
                if ((last_status == SD_STATUS_NOT_EXIST) && (g_sd_status == SD_STATUS_ABNORMAL))
                {
                    tuya_ipc_sd_mount();
                    sd_state = SDCARD_STATE_MOUNTINT;
                    last_status = SD_STATUS_UNKNOWN;
                    PRT_INFO(DEF_TAG, "first mount SD card\n");
                    continue;
                }
                else
                {
                    IPC_APP_report_sd_status_changed(g_sd_status);
                }
            }
            last_status = g_sd_status;

            switch (g_sd_status)
            {
            case SD_STATUS_UNKNOWN:
                sd_state = SDCARD_STATE_UNKNOWN;
                break;

            case SD_STATUS_NORMAL:
            {
                if (sd_state == SDCARD_STATE_MOUNTINT)
                {
                    PRT_INFO(DEF_TAG, "sd card mount ok\n");
                    sd_state = SDCARD_STATE_START_RECORD;
                }
            }
            break;

            case SD_STATUS_ABNORMAL:
            {
                PRT_INFO(DEF_TAG, "sd abnormal\n");
                if (sd_state == SDCARD_STATE_MOUNTINT)
                {
                    tuya_ipc_sd_remount();
                }
                else
                {
                    tuya_ipc_sd_mount();
                    sd_state = SDCARD_STATE_MOUNTINT;
                }
            }
            break;

            case SD_STATUS_NOT_EXIST:
            {
                sd_state = (sd_state == SDCARD_STATE_START_RECORD) ? SDCARD_STATE_UMOUNT : sd_state;
                if (sd_state == SDCARD_STATE_UMOUNT)
                {
                    tuya_ipc_sd_umount();
                    sd_state = SDCARD_STATE_UNKNOWN;
                }
            }
            break;

            default:
                break;
            }
        }
        sleep(1);
    }
#endif /* end of CMAKE_SUPPORT_SD_RECORD */

    PRT_INFO(DEF_TAG, "sd card exit\n");
    return NULL;
}

int tuya_ipc_sd_status_upload(int status)
{
    // IPC_APP_report_sd_status_changed(status);
    return 0;
}

Com_void InSdk_SdCardCheck_Init(Com_void)
{
    pthread_t id;

    pthread_create(&id, NULL, check_sdcard_status_thread, NULL);
    pthread_detach(id);
}
